package doc2db.interfaces.xmledit;

import doc2db.Launcher;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.io.File;
import java.lang.reflect.Method;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.InputMap;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JTextPane;
import javax.swing.KeyStroke;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.StyledDocument;
import javax.swing.undo.UndoManager;
import org.opu.stylepane.Ostermiller.Syntax.HighlightedDocument;

/**
 *
 * @author yura
 */
public class MTextArea extends JTextPane {
    /** Font of popup menu */
    protected Font menufont;

    /** Undo-redo manager */
    protected UndoManager undoManager;

    /** Dir name with icons */
    protected static String iconPath = ".";

    /** Insert long space instead of tabs */
    private static boolean noTab;

    /**
     *  Creates a new MTextArea, with a HighlightedDocument document model.
     * To this document addUndoableEditListener (new UndoManager()).
     * And setted specific highlight style - lexer
     * @see UndoManager
     * @see HighlightedDocument
     * @see HighlightedDocument#setHighlightStyle(java.lang.Object)
     * @param lexer language lexer
     */
    public MTextArea (Object lexer){
        this(new HighlightedDocument());
        HighlightedDocument doc = (HighlightedDocument) (getDocument());
        doc.setHighlightStyle(lexer);
    }

    /**
     *  Creates a new MTextArea, with a specified document model.
     * A new instance of javax.swing.text.StyledEditorKit is created and set.
     * To this document addUndoableEditListener (new UndoManager()).
     * @see UndoManager
     * @param doc the document model
     */
    public MTextArea(StyledDocument doc) {
        super(doc);
        undoManager = new UndoManager();
        undoManager.setLimit(2000);
        menufont = new Font(Font.MONOSPACED, Font.PLAIN, 12);
        addInputActions();
        getDocument().addUndoableEditListener(undoManager);
        setCursor(new Cursor(Cursor.TEXT_CURSOR));
        setComponentPopupMenu(createPopupMenu());
        setBackground(Color.WHITE);
    }

    /**
     * Return dir name with icons
     * @return dir name with icons
     */
    public static String getIconPath() {
        return iconPath;
    }

    /**
     * Set dir name with icons
     * @param iconPath dir name with icons
     */
    public static void setIconPath(String iconPath) {
        MTextArea.iconPath = iconPath;
    }

    /**
     * Return true if insert long space instead of tabs
     * @return true if insert long space instead of tabs
     */
    public static boolean isNoTab() {
        return noTab;
    }

    /**
     * set is`t needed to insert long space instead of tabs
     * @param noTab if true insert long space instead of tabs
     */
    public static void setNoTab(boolean noTab) {
        MTextArea.noTab = noTab;
    }

    /**
     * Return font of popup menu
     * @return font of popup menu
     */
    public Font getMenufont() {
        return menufont;
    }

    /**
     * Set font of popup menu
     * @param menufont font of popup menu
     */
    public void setMenufont(Font menufont) {
        this.menufont = menufont;
    }

    /**
     *  Undoes the appropriate edits.
     */
    public void undo(){
        if (undoManager.canUndo()) {
            undoManager.undo();
        }
    }

    /**
     * Redoes the appropriate edits.
     */
    public void redo() {
        if (undoManager.canRedo()) {
            undoManager.redo();
        }
    }

    /**
     * Get popup menu for curent TextArea
     * @return popup menu for curent TextArea
     */
    public JPopupMenu createPopupMenu() {
        JPopupMenu popup = new JPopupMenu();
        addMenuItem("undo",         "Предыдущее",    popup);
        addMenuItem("redo",         "Последующее",   popup);
        popup.addSeparator();
        addMenuItem("cut",          "Вырезать",      popup);
        addMenuItem("copy",         "Копировать",    popup);
        addMenuItem("paste",        "Вставить",      popup);
        popup.addSeparator();
        addMenuItem("select-all",   "Выделить всё",  popup);
        popup.addSeparator();
        addMenuItem("delete",       "Удалить",       popup);
        addMenuItem("delete-line",  "Удалить линию", popup);
        popup.addSeparator();
        addMenuItem("change-case",  "Инвертировать", popup);
        addMenuItem("to-lower",     "Опустить",      popup);
        addMenuItem("to-upper",     "Поднять",       popup);
        popup.setFont(menufont);
        return popup;
    }

    /**
     *  Get menu iteam with name - 'action', label - 'label',
     * icon = 'iconPath + action', and action command lunch
     * method 'action' from curent MTextArea
     * @param action action of menu iteam
     * @param label label of menu iteam
     * @see Method
     * @see Method#invoke(java.lang.Object, java.lang.Object[])
     * @return menu iteam
     */
    public JMenuItem addMenuItem(String action, String label, JPopupMenu popup) {
        Action shellAction = getActionMap().get(action);
        if (shellAction == null){
            return null;
        }

        JMenuItem mi = new JMenuItem(shellAction);
        Icon icon = getIcon(action);

        mi.setIcon(icon);
        mi.setText(label);
        mi.setFont(menufont);
        
        popup.add(mi);

        return mi;
    }

    private void addInputActions() {
        ActionMap actionMap = getActionMap();
        actionMap.put("undo", new UndoAction("undo", null));
        actionMap.put("redo", new RedoAction("redo", null));
        actionMap.put("to-lower", new ToLowerAction("to-lower", null));
        actionMap.put("to-upper", new ToUpperAction("to-upper", null));
        actionMap.put("change-case", new ChangeCaseAction("change-case", null));
        actionMap.put("delete", new DeleteAction("delete", null));
        actionMap.put("delete-line", new DeleteLineAction("delete-line", null));

        InputMap inputMap = getInputMap();
        inputMap.put(KeyStroke.getKeyStroke("control Z"), "undo");
        inputMap.put(KeyStroke.getKeyStroke("control Y"), "redo");
        inputMap.put(KeyStroke.getKeyStroke("control shift Z"), "redo");
        inputMap.put(KeyStroke.getKeyStroke("F2"), "to-upper");
        inputMap.put(KeyStroke.getKeyStroke("F3"), "to-lower");
        inputMap.put(KeyStroke.getKeyStroke("shift F2"), "change-case");
    }

    public Icon getIcon(String action) {
        String imgName = action + ".png";
        if (iconPath == null || "null".equals(iconPath)) {
            return Launcher.getIcon(imgName);
        } else {
            String path = new File(iconPath, imgName).getAbsolutePath();
            Icon img = new ImageIcon(path);

            if ((img == null) || (img.getIconWidth() < 0)){
                img = Launcher.getIcon("menu/editor/" + imgName);
            }

            return img;
        }
    }

    @Override
    public void setFont(Font font) {
        super.setFont(font);
        Document doc = getDocument();
        if (doc instanceof HighlightedDocument){
            HighlightedDocument hdoc = (HighlightedDocument) doc;
            hdoc.setFontSize(font.getSize());
            hdoc.setFontName(font.getFontName());
        }
    }

    /**
     * Undoes the appropriate edits.
     */
    public class UndoAction extends AbstractAction {

        public UndoAction(String name, Icon icon) {
            super(name, icon);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (undoManager.canUndo()) {
                undoManager.undo();
            }
        }
    }

    /**
     * Redoes the appropriate edits.
     */
    public class RedoAction extends AbstractAction {

        public RedoAction(String name, Icon icon) {
            super(name, icon);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (undoManager.canRedo()) {
                undoManager.redo();
            }
        }
    }

    /**
     * Convert selected, or first after cursor, characters to lower case
     */
    public class ToLowerAction extends AbstractAction {

        public ToLowerAction(String name, Icon icon) {
            super(name, icon);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            int start = getSelectionStart();
            int end = getSelectionEnd();

            if (end > getText().length()){
                return;
            }

            if (end == start){
                end ++;
                if (end > getText().length()){
                    return;
                }
                setSelectionEnd(end);
                replaceSelection(getSelectedText().toLowerCase());
                setSelectionStart(end);
            } else {
                replaceSelection(getSelectedText().toLowerCase());
            }
        }
    }

    /**
     * Convert selected, or first after cursor, characters to upper case
     */
    public class ToUpperAction extends AbstractAction {

        public ToUpperAction(String name, Icon icon) {
            super(name, icon);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            int start = getSelectionStart();
            int end = getSelectionEnd();

            if (end > getText().length()){
                return;
            }

            if (end == start){
                end ++;
                if (end > getText().length()){
                    return;
                }
                setSelectionEnd(end);
                replaceSelection(getSelectedText().toUpperCase());
                setSelectionStart(end);
            } else {
                replaceSelection(getSelectedText().toUpperCase());
            }
        }
    }

    /**
     * Convert selected case
     */
    public class ChangeCaseAction extends AbstractAction {

        public ChangeCaseAction(String name, Icon icon) {
            super(name, icon);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            int start = getSelectionStart();
            int end = getSelectionEnd();

            if (end > getText().length()){
                return;
            }

            if (end == start){
                end ++;
                if (end > getText().length()){
                    return;
                }
                setSelectionEnd(end);
                replaceSelection(reverse(getSelectedText()));
                setSelectionStart(end);
            } else {
                replaceSelection(reverse(getSelectedText()));
            }
        }

        private String reverse(String str){
            StringBuilder sb = new StringBuilder();
            char[] arr = str.toCharArray();
            for (char c : arr) {
                if (Character.isLowerCase(c)){
                    sb.append(Character.toUpperCase(c));
                } else if (Character.isUpperCase(c)){
                    sb.append(Character.toLowerCase(c));
                } else {
                    sb.append(c);
                }
            }
            return sb.toString();
        }
    }

    /**
     * delete selection, if now selection delete next char
     */
    public class DeleteAction extends AbstractAction {

        public DeleteAction(String name, Icon icon) {
            super(name, icon);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            int start = getSelectionStart();
            int end = getSelectionEnd();
            Document doc = getDocument();

            if (end == start){
                end ++;
            }

            if (end > doc.getLength()){
                return;
            }

            try {
                doc.remove(start, end - start);
            } catch (BadLocationException ex) {
                System.err.println("method delete - " + ex);
            }
        }
    }
    
    /**
     * delete curent line
     */
    public class DeleteLineAction extends AbstractAction {

        public DeleteLineAction(String name, Icon icon) {
            super(name, icon);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            int start = getSelectionStart();
            int end = getSelectionEnd();
            int startDel = start;
            int endDel = end;

            Element root = getDocument().getDefaultRootElement();
            int elementCount = root.getElementCount();


            for (int i = 0; i < elementCount; i++) {
                int startOffset = root.getStartOffset();
                int endOffset = root.getEndOffset();
                if (start >= startOffset && start <= endOffset) {
                    startDel = startOffset;
                }
                if (end <= endOffset && end >= startOffset){
                    endDel = startDel - endOffset;
                }
            }

            try {
                getDocument().remove(startDel, endDel);
            } catch (BadLocationException ex) {
                System.err.println("method delete line - " + ex);
            }
        }
    }

}
